﻿#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using Beaker.OpenCube.Nbt;
using Beaker.OpenCube.Specs.Builders;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

#endregion

namespace Beaker.OpenCube.Specs.Nbt
{
	[ExcludeFromCodeCoverage]
	[TestClass]
	public class NbtReaderSpecs
	{
		[TestMethod]
		public void EmptyRootCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound.Should().NotBeNull();
			compound.Count.Should().Be( 0 );
		}

		[TestMethod]
		public void ByteInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.Byte, "content" ).Append( (sbyte)0x5e )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound["content"].Should().Be( (sbyte)0x5e );
		}

		[TestMethod]
		public void ShortInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.Short, "content" ).Append( (short)0x165e )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound["content"].Should().Be( (short)0x165e );
		}

		[TestMethod]
		public void IntInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.Int, "content" ).Append( 0x16a0f5ea )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound["content"].Should().Be( 0x16a0f5ea );
		}

		[TestMethod]
		public void LongInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.Long, "content" ).Append( 0x4ae4c18516a0f5ea )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound["content"].Should().Be( 0x4ae4c18516a0f5ea );
		}

		[TestMethod]
		public void FloatInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.Float, "content" ).Append( -4.641f )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound["content"].Should().Be( -4.641f );
		}

		[TestMethod]
		public void DoubleInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.Double, "content" ).Append( -4.641d )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound["content"].Should().Be( -4.641d );
		}

		[TestMethod]
		public void ByteArrayInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.ByteArray, "content" )
					.Append( 6 )
					.Append( new sbyte[] { 0x56, 0x12, -0x2a, -0x76, -0x01, 0x30 } )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			var data = (IEnumerable<sbyte>)compound["content"];
			data.Should().Equal( new sbyte[] { 0x56, 0x12, -0x2a, -0x76, -0x01, 0x30 } );
		}

		[TestMethod]
		public void StringInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.String, "content" ).Append( "Chocolademouse" )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			compound["content"].Should().Be( "Chocolademouse" );
		}

		[TestMethod]
		public void ListInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.List, "content" )
					.Append( (byte)NbtTag.Float )
					.Append( 4 )
					.Append( 4.7613f ).Append( -96.7722f ).Append( 776742f ).Append( -986857.837729f )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			var data = (IEnumerable<float>)compound["content"];
			data.Should().Equal( new[] { 4.7613f, -96.7722f, 776742f, -986857.837729f } );
		}

		[TestMethod]
		public void ListOfListsInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.List, "content" )
					.Append( (sbyte)NbtTag.List )
						.Append( 2 )
							.Append( (sbyte)NbtTag.Int )
								.Append( 2 )
									.Append( 10 )
									.Append( 20 )
							.Append( (sbyte)NbtTag.Byte )
								.Append( 3 )
									.Append( (sbyte)30 )
									.Append( (sbyte)40 )
									.Append( (sbyte)50 )
						.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			var data = (List<object>)compound["content"];
			var list1 = (List<int>)data[0];
			var list2 = (List<sbyte>)data[1];

			list1.Count.Should().Be( 2 );
			list1.Should().Equal( new[] { 10, 20 } );

			list2.Count.Should().Be( 3 );
			list2.Should().Equal( new sbyte[] { 30, 40, 50 } );
		}

		[TestMethod]
		public void ListOfCompoundsInCompound()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" )
					.Append( NbtTag.List, "content" )
						.Append( (sbyte)NbtTag.Compound )
						.Append( 2 )
							.Append( NbtTag.String, "a string" ).Append( "This is a string" )
							.Append( NbtTag.End )
							.Append( NbtTag.String, "another string" ).Append( "FooBar" )
							.Append( NbtTag.Double, "a double" ).Append( -94.95672d )
							.Append( NbtTag.End )
					.Append( NbtTag.End )
					.Build();

			// act
			NbtDictionary compound = NbtReader.Deserialize( stream );

			// assert
			var data = (List<NbtDictionary>)compound["content"];
			NbtDictionary compound1 = data[0];
			NbtDictionary compound2 = data[1];

			compound1.Count.Should().Be( 1 );
			( (string)compound1["a string"] ).Should().Be( "This is a string" );

			compound2.Count.Should().Be( 2 );
			( (string)compound2["another string"] ).Should().Be( "FooBar" );
			( (double)compound2["a double"] ).Should().Be( -94.95672d );
		}

		[TestMethod]
		public void FileTestFromWikiVgNbt()
		{
			// test taken from: http://wiki.vg/NBT
			// give file should parse without errors

			// arrange
			Stream stream = GetType().Assembly.GetManifestResourceStream( GetType().Namespace + ".bigtest.nbt" );

			// act
			NbtDictionary content = NbtReader.Deserialize( stream );

			// assert 
			// test against content as specified in wiki
			content.Count.Should().Be( 11 );
			{
				var nestedCompoundTest = (NbtDictionary)content["nested compound test"];
				{
					nestedCompoundTest.Count.Should().Be( 2 );
					var egg = (NbtDictionary)nestedCompoundTest["egg"];
					{
						egg.Count.Should().Be( 2 );
						( (string)egg["name"] ).Should().Be( "Eggbert" );
						( (float)egg["value"] ).Should().Be( 0.5f );
					}
					var ham = (NbtDictionary)nestedCompoundTest["ham"];
					{
						ham.Count.Should().Be( 2 );
						( (string)ham["name"] ).Should().Be( "Hampus" );
						( (float)ham["value"] ).Should().Be( 0.75f );
					}
				}
				( (int)content["intTest"] ).Should().Be( 2147483647 );
				( (sbyte)content["byteTest"] ).Should().Be( 127 );
				( (string)content["stringTest"] ).Should().Be( "HELLO WORLD THIS IS A TEST STRING " +
					Encoding.UTF8.GetString( new byte[] { 0xc3, 0x85, 0xc3, 0x84, 0xc3, 0x96 } ) + "!" );
				var listTestLong = (List<long>)content["listTest (long)"];
				{
					listTestLong.Should().Equal( new long[] { 11, 12, 13, 14, 15 } );
				}
				( (double)content["doubleTest"] ).Should().Be( 0.49312871321823148d );
				( (float)content["floatTest"] ).Should().Be( 0.49823147058486938f );
				( (long)content["longTest"] ).Should().Be( 9223372036854775807L );
				var listTestCompound = (List<NbtDictionary>)content["listTest (compound)"];
				{
					listTestCompound.Count.Should().Be( 2 );
					listTestCompound[0].Count.Should().Be( 2 );
					{
						( (long)listTestCompound[0]["created-on"] ).Should().Be( 1264099775885L );
						( (string)listTestCompound[0]["name"] ).Should().Be( "Compound tag #0" );
					}
					listTestCompound[1].Count.Should().Be( 2 );
					{
						( (long)listTestCompound[1]["created-on"] ).Should().Be( 1264099775885L );
						( (string)listTestCompound[1]["name"] ).Should().Be( "Compound tag #1" );
					}
				}

				var byteArraytest = new sbyte[1000];
				for( int n = 0; n < 1000; ++n )
					byteArraytest[n] = (sbyte)( ( n * n * 255 + n * 7 ) % 100 );

				( (sbyte[])content["byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"] )
					.Should().Equal( byteArraytest );

				( (short)content["shortTest"] ).Should().Be( 32767 );
			}
		}

		[TestMethod]
		public void StreamMustStartWithCompoundTagThrowIfNot()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Double ) // start  with double
					.Build();

			// act
			Action act = () => NbtReader.Deserialize( stream );

			// assert
			act.ShouldThrow<NbtFileFormatException>();
		}

		[TestMethod]
		public void TagInStreamMustBeLegalThrowIfNot()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" ) // start unnamed compound
					.Append( (NbtTag)0x7e )	// first node will start with invalid tag
					.Build();

			// act
			Action act = () => NbtReader.Deserialize( stream );

			// assert
			act.ShouldThrow<NbtFileFormatException>();
		}

		[TestMethod]
		public void PrematureEndOfStreamDueToMissingNameShouldThrow()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" ) // start unnamed compound
					.Append( NbtTag.String )	// string, but missing name
					.Build();

			// act
			Action act = () => NbtReader.Deserialize( stream );

			// assert
			act.ShouldThrow<EndOfStreamException>();
		}

		[TestMethod]
		public void PrematureEndOfStreamDueToShortNameShouldThrow()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" ) // start unnamed compound
					.Append( NbtTag.String )	// string, but missing name
					.Append( (byte)0x00 ).Append( (byte)0x10 ) // should have 16 byte
					.Append( (byte)'S' ) // but only one in file
					.Build();

			// act
			Action act = () => NbtReader.Deserialize( stream );

			// assert
			act.ShouldThrow<EndOfStreamException>();
		}

		[TestMethod]
		public void PrematureEndOfStreamDueToMissingDataShouldThrow()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" ) // start unnamed compound
					.Append( NbtTag.Int, "FooBar" ) // int name foobar
					.Append( (byte)0x12 ).Append( (byte)0x6e ).Append( (byte)0x50 ) // 3 instead of 4 bytes
					.Build();

			// act
			Action act = () => NbtReader.Deserialize( stream );

			// assert
			act.ShouldThrow<EndOfStreamException>();
		}

		[TestMethod]
		public void PrematureEndOfStreamDueToMissingEndTagShouldThrow()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" ) // start unnamed compound
					.Append( NbtTag.Int, "FooBar" ) // int name foobar
					.Append( 0x6e27fa72 ) // int
				// no end tag to end compound
					.Build();

			// act
			Action act = () => NbtReader.Deserialize( stream );

			// assert
			act.ShouldThrow<EndOfStreamException>();
		}

		[TestMethod]
		public void InvalidUtf8StreamShouldThrow()
		{
			// arrange
			Stream stream =
				new NbtStreamBuilder()
					.Append( NbtTag.Compound, "" ) // start unnamed compound
					.Append( NbtTag.String, "FooBar" ) // string name foobar
					.Append( (short)2 ) // 2 bytes in string
					.Append( (byte)0xc3 ).Append( (byte)0x2e ) // 2 bytes, but invalid utf8
					.Build();

			// act
			Action act = () => NbtReader.Deserialize( stream );

			// assert
			act.ShouldThrow<NbtFileFormatException>();
		}


	}
}
